feat(transaction-pay-controller): add generic non-atomic post-Relay flow via atomic and recipient - #9497
Merged
Merged
Conversation
OGPoyraz
marked this pull request as ready for review
July 14, 2026 10:50
OGPoyraz
force-pushed
the
ogp/enable-max-withdraw-to-ma
branch
from
July 14, 2026 11:17
8e85d4e to
b13f8fe
Compare
4 tasks
OGPoyraz
force-pushed
the
ogp/enable-max-withdraw-to-ma
branch
from
July 20, 2026 07:57
db08be4 to
52ada7a
Compare
OGPoyraz
commented
Jul 20, 2026
OGPoyraz
commented
Jul 20, 2026
OGPoyraz
commented
Jul 20, 2026
matthewwalsh0
requested changes
Jul 20, 2026
Cross-chain flows (e.g. Predict withdraw on Polygon depositing to a Money Account on Monad) carry the deposit in the Relay quote's destination txs[] with a delegation signed for the destination chain. Prepending it onto the source-chain execute batch made that delegation get redeemed on the source chain, so the on-chain signature check recovered a wrong signer and reverted with InvalidEOASignature() (0x3db6791c). Only prepend the override for same-chain flows; cross-chain flows fall through to prepend the original tx.
OGPoyraz
force-pushed
the
ogp/enable-max-withdraw-to-ma
branch
from
July 22, 2026 07:50
23233f7 to
acd0987
Compare
atomic and recipient
10 tasks
matthewwalsh0
requested changes
Jul 23, 2026
matthewwalsh0
requested review from
matthewwalsh0
and removed request for
matthewwalsh0
July 27, 2026 23:08
matthewwalsh0
requested changes
Jul 28, 2026
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit cf0519b. Configure here.
matthewwalsh0
approved these changes
Jul 31, 2026
pull Bot
pushed a commit
to Reality2byte/metamask-mobile
that referenced
this pull request
Aug 4, 2026
…y deposit (MetaMask#33489) <!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until this PR meets the canonical Definition of Ready For Review in `docs/readme/ready-for-review.md`. In short: the template must be materially complete (not just section titles present), all status checks must be currently passing, and the only expected follow-up commits must be reviewer-driven. --> <!-- mms-check directive vocabulary — read by .github/scripts/shared/pr-template-checks.ts at module load to build the validation plan. Directives are invisible in rendered markdown and must NOT be removed or edited without updating the validator registry. type=text Section must contain non-placeholder prose. type=changelog Section must have a valid CHANGELOG entry: line. type=issue-link Section must have a Fixes:/Closes:/Refs: line with a value. type=manual-testing Section must have real testing steps or an explicit N/A. type=screenshot Section must have evidence (image/URL) or an explicit N/A. type=checklist Section must have all checkboxes consciously checked. required=true|false Whether a missing/invalid section runs the validator at all. blocking=true|false Whether a failure of this check fails the CI workflow. Default: false — failures are shown as warnings in the sticky comment but do not block the PR. Sections without a directive are checked for structural presence only. --> ## **Description** <!-- mms-check: type=text required=true --> Consumes the new `atomic` primitive from `@metamask/transaction-pay-controller` (see [core#9497](MetaMask/core#9497)) to enable two previously-blocked Money Account flows: - **Perps/Predict withdraw → Money Account** — post-quote flow whose destination is mUSD on Monad. Settled amount is only known after Relay completes; the transfer to MA now runs as a sponsored post-Relay batch. All perps/predict withdraws to MA take this non-atomic path (a max flag cannot be set for these flows, and EXACT_INPUT keeps fee semantics consistent with standard withdraws). - **Max-amount Money Account deposit** — `isMaxAmount` forces EXACT_INPUT so the vault deposit cannot be pre-encoded at quote time. The vault approve + deposit now runs post-Relay against the actually-settled mUSD. The `atomic: false` flag on the transaction config drives the non-atomic path in TPC. There is no client-configurable `recipient`: TPC derives where the Relay output settles at quote time — via this app's `getPaymentOverrideData` callback (which returns the Money Account address) for post-quote flows, or the transaction's own `from` for max deposits. Regular (non-max) Money Account deposits and Money Account withdraws keep the existing atomic path unchanged. ### Changes - **`transaction-pay.ts` (`applyMoneyAccountOverride`)** — takes `transactionMeta` and derives whether to set `atomic: false` (Perps/Predict withdraw) or `refundTo` (Money Account deposit). Money Account withdraw only gets `paymentOverride`. - **`transaction-pay.ts` (`setMoneyAccountDepositMaxAtomic`)** — new helper that flips `config.atomic` on the max toggle for Money Account deposits. - **`useTransactionCustomAmount.ts` (`setIsMax`)** — calls `setMoneyAccountDepositMaxAtomic` when the transaction is a Money Account deposit, so toggling max on/off correctly flips the non-atomic path. - **`useClearPaymentOverride.ts`** — clears `atomic` alongside `paymentOverride` and `refundTo` when the user switches away from Money Account pay-with. - **`paymentoverride-callback.ts`** — forks on `transactionData.atomic`: atomic path keeps the delegation-wrapped calls; non-atomic path returns raw `[approve, deposit]` (post-quote deposit) or `[withdraw, transfer]` (withdraw to MA) so TPC can submit the second leg as a sponsored batch from the Money Account. The returned `recipient` (Money Account address) is what TPC uses to settle the Relay quote at quote time. - **`useInsufficientPredictBalanceAlert.ts`** — fixes the fee check for predict withdraws: they are EXACT_INPUT (fees deducted from the receive amount, never added on top), so the old `amount + fees > balance` check wrongly blocked near-full-balance withdraws. Now mirrors perps: alert only when fees consume the entire amount. - **`useInsufficientPerpsBalanceAlert.ts`** — drops the atomic-era `amount + fees > balance` check for MA-override withdraws; under the non-atomic flow they are EXACT_INPUT like standard withdraws. The plain `balance < amount` and `fees >= amount` checks apply to all perps withdraws. - **Patch files** — re-generated `@metamask/transaction-pay-controller` and `@metamask/transaction-controller` patches to match the new core API surface. ## **Changelog** <!-- mms-check: type=changelog required=true blocking=true --> CHANGELOG entry: Enabled Perps/Predict withdraws to Money Account and max-amount Money Account deposits via the new post-Relay sponsored deposit flow. ## **Related issues** <!-- mms-check: type=issue-link required=true --> Fixes: ## **Manual testing steps** <!-- mms-check: type=manual-testing required=true --> ```gherkin Feature: Perps/Predict withdraw to Money Account Scenario: user withdraws from Perps to Money Account Given user has a Perps position with USDC balance on HyperLiquid And user has a Money Account When user opens the Perps withdraw flow And user selects Money Account as the destination And user enters an amount (any amount, including full balance) Then a quote appears with the entered amount as source amount When user confirms the transaction Then Relay settles mUSD directly on the Money Account And a sponsored batch approves + deposits the settled mUSD into the vault Scenario: user max-deposits into Money Account Given user has mUSD on Monad And user has a Money Account When user opens the Money Account deposit flow And user taps "Max" Then a quote appears with the full balance as source amount When user confirms the transaction Then Relay settles mUSD to the Money Account address And a sponsored batch approves + deposits mUSD into the vault Scenario: regular (non-max) Money Account deposit continues to work Given user has mUSD on Monad And user has a Money Account When user opens the Money Account deposit flow And user types a specific (non-max) amount Then a quote appears with the typed target amount When user confirms the transaction Then the atomic embedded vault deposit runs as before Scenario: Money Account withdraw to Perps continues to work Given user has mUSD in a Money Account And user has a Perps account When user opens the Money Account withdraw flow And user selects Perps as the destination And user types an amount Then a quote appears When user confirms the transaction Then the vault withdraw + transfer + Relay bridge complete atomically ``` ## **Screenshots/Recordings** <!-- mms-check: type=screenshot required=true --> <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** N/A — flow was blocked (no quote available for max Perps/Predict withdraw to MA and max MA deposit). ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** <!-- mms-check: type=checklist required=true --> <!-- Every checklist item must be consciously assessed before marking this PR as "Ready for review". A checked box means you deliberately considered that responsibility, not that you literally performed every action listed. Unchecked boxes are ambiguous: they are not an implicit "N/A" and they are not a silent "skip". See `docs/readme/ready-for-review.md` for the full checklist semantics. --> - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### Performance checks (if applicable) - [ ] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [ ] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [ ] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** <!-- Reviewer checklist items follow the same semantics as the author checklist: an unchecked box is ambiguous, a checked box means the reviewer consciously assessed that responsibility. See `docs/readme/ready-for-review.md`. --> - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **High Risk** > Changes payment override, atomic batching, and vault deposit/withdraw submission paths for real money flows; incorrect atomic or fee logic could block quotes or mis-settle funds. > > **Overview** > Wires **`atomic: false`** through Money Account transaction pay so Perps/Predict withdraws to the Money Account and **max** Money Account deposits can run vault steps as a **post-Relay** sponsored batch instead of only inside the atomic bundle. > > **`applyMoneyAccountOverride`** now takes **`transactionMeta`** and sets **`atomic: false`** for Perps/Predict withdraw, **`refundTo`** for deposit-direction flows, and nothing extra for Money Account withdraw. **`setMoneyAccountDepositMaxAtomic`** toggles **`atomic`** when max is selected on Money Account deposit; **`useTransactionCustomAmount`** calls it from **`setIsMax`** and treats predict withdraw like perps for max (**`isMaxAmount`** stays off). **`useClearPaymentOverride`** clears **`atomic`** unless a max Money Account deposit still needs **`atomic: false`**. > > **`getPaymentOverrideData`** branches on **`atomic`**: non-atomic paths return raw withdraw/transfer or approve/deposit calls without delegation wrap. > > **`hasMax`** is enabled on Money Account deposit and Predict withdraw UIs. Insufficient-balance alerts for predict and perps withdraws now use **`getTotalPayFeesUsd`** and only flag fees that **consume the entire** entered amount (EXACT_INPUT), removing the old **`amount + fees > balance`** logic for Money Account override perps withdraws. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 8d1b512. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Explanation
Introduces a generic non-atomic post-Relay flow on
transaction-pay-controller, driven by a new field onTransactionConfig/TransactionData:atomic?: boolean— whenfalse, TPC skips atomic quote embedding (noprocessTransactions, noprocessMoneyAccountPostQuote). The Relay quote bridges/swaps only, then a second leg runs after completion.This unblocks two previously-blocked flows without adding another bespoke MA-specific module:
isMaxAmountforces EXACT_INPUT, so vault calls cannot be pre-encoded at quote time.For both, Relay settles mUSD directly to a recipient derived internally at quote time (no client-configurable
recipientfield); once complete, a sponsoredaddTransactionBatchruns the second leg (approve + teller deposit for post-quote flows; the transaction's own nested calls for non-post-quote flows).Recipient derivation (quote time)
resolveNonAtomicRecipientinrelay-quotes.tsderives where the Relay output must settle for non-atomic flows:getPaymentOverrideDatacallback — the same source of truth the atomic path already uses viaprocessMoneyAccountPostQuote— and uses its returnedrecipient(the Money Account address, which cannot be derived from the request). Falls back tofromif the callback returns no recipient.txParams.from, which is the Money Account rather than theaccountOverride-resolved funding EOA. Without this, max deposits settled mUSD on the EOA where the sponsored vault batch and CHOMP backup could not reach it.The derived recipient is persisted on
quote.request.recipient(internal-only) so the post-completion leg reads/settles against the correct account.Changes
types.ts— AddsatomictoTransactionConfigandTransactionData.QuoteRequest.recipientis now internal-only (derived, not client-set).TransactionPayController.ts— PropagatesatomicthroughtransactionData↔configsynchronisation (via spread +Object.assign).utils/quotes.ts— ThreadsatomicthroughbuildQuoteRequests/buildPostQuoteRequests.strategy/relay/relay-quotes.tsresolveNonAtomicRecipient(see above); the derived recipient feeds the quote body and the normalized quote request.processTransactionsnow returns a boolean and encapsulates all its skip conditions (skipProcessTransactions/isPostQuote, Polymarket deposit wallet, non-atomic) in a single early-return, sogetSingleQuoteroutes to the alternate handlers when it declines.strategy/relay/relay-submit.ts— After successful Relay completion, whenatomic === false, runssubmitPostNonAtomic:resolveSettledAmount:submitTransactions— the transaction that actually moved the funds — is read instead. Relay execute submissions return theFALLBACK_HASHplaceholder rather than a real hash, so only in that case does the quote's minimum output remain as the last available source.isPostQuote === true, callsgetPaymentOverrideDatawith the settled amount to build the deposit batch; prefers the callback's returnedrecipientas source of truth for the second-leg account. ThrowsMissing post-quote deposit callsif the callback returns empty — post-quote parent metas have no vault-side nested calls, so falling through togetAmountDatawould strand funds on the recipient.isPostQuote === false, falls through to the transaction's own nested calls re-encoded viagetAmountData.submitMoneyAccountVaultDeposit.utils/ma-vault-deposit.ts— ExtendssubmitMoneyAccountVaultDepositwith two new optional params:moneyAccountAddress— explicit MA address override (used whentxParams.fromisn't the MA, e.g. Perps/Predict withdraws).depositCalls— pre-built batch. When provided,resolveVaultDepositBatchreturns it directly, skipping thegetAmountDatare-encoding path.Removed —
strategy/relay/relay-post-ma-vault.tsand its tests. The dedicated MA post-Relay module is replaced by the genericsubmitPostNonAtomicinrelay-submit.ts.Same-chain payment-override prepend gate
Also fixes a pre-existing bug in
relay-submit.ts: thepaymentOverrideprepend onto the source execute batch is skipped for non-atomic flows — their second leg is submitted separately after completion, so prepending would double-embed the vault deposit. Cross-chain atomic flows (e.g.moneyAccountWithdrawon Monad settling USDC on Arbitrum for Perps) still prepend the source-side vault withdraw + transfer so Relay has funds to bridge.References
Checklist
Note
High Risk
Changes Relay quote construction, post-completion settlement, and Money Account vault submission paths where incorrect amounts or recipients could strand user funds.
Overview
Adds
atomic?: booleanonTransactionConfig/TransactionData/QuoteRequest(default true). Whenatomicisfalse, Relay quotes only bridge to an internally derivedrecipient—no embeddedtxs—and the second leg (vault deposit) runs after Relay succeeds via newsubmitPostNonAtomicinrelay-submit.ts.Quote time:
resolveNonAtomicRecipientsets the bridge recipient (post-quote:getPaymentOverrideData; max-deposit: parent txfrom).processTransactionsreturns a boolean and skips embedding for non-atomic flows.Submit time: Settled amount comes from on-chain Transfer logs when possible (stricter than the removed module); post-quote legs use
getPaymentOverrideDatawith the settled human amount and optionaldepositCalls/moneyAccountAddressonsubmitMoneyAccountVaultDeposit. Non-atomic flows skippaymentOverrideprepend to avoid double-embedding the vault step.Removed:
relay-post-ma-vault.tsand its tests—replaced by the generic path above.Reviewed by Cursor Bugbot for commit bbc766e. Bugbot is set up for automated code reviews on this repo. Configure here.